local skynet = require "skynet"
local mc = require "multicast"
local packdeal = require "packdeal"
local queue = require "skynet.queue"  
local json   = require "cjson" 


local NetCmd = require "common/NetCmd"
local Common = require "common/Common"
local ErrNo = require "common/ErrNo"

UID_LOGIN = 1
ACCOUNT_LOGIN = 2 
NEED_AUTH = true

local send_package = packdeal.send_package
local unpack_package = packdeal.unpack_package

CMD = {}
POST = {}
local SOCKET = {}
local REQUEST = {}
local gate
local agent = {}    -- 1 agent_address, 2 account, 3 uid
local account_to_agent = {}
local uid_to_agent = {}
local queue_dict = {}
local client_ip_arr = {}
local time_out_stop = {}

-- 每个账户对应一个队列，防止重入
function make_queue(fd,data,func)
    local account = data.account
    if not account then
    	send_package(fd, {cmd=NetCmd.S2C_VERSION, uid=0})
        -- skynet.error(string.format("invalid json cmd:%d, fd:%d, account nil", json_data.cmd, fd))
        return
    end

    if not queue_dict[account] then
        queue_dict[account] = queue()
    end
    local cs = queue_dict[account]
    local function closure()
        func(fd,data)
    end
    cs(closure)
end

-- 用户登陆
local login_mode = UID_LOGIN
function REQUEST.login(fd,data)
    local ok = false
    local msg = ""
    --local account = data.account
    --local token = data.token
	local uid = nil
	local account = nil
	local password = nil
	local expires_time = 0
	local cmd_rs = NetCmd.S2C_LOGIN_ACCOUNT
	local client_ip = client_ip_arr[fd] and client_ip_arr[fd] or ''
	if data.ip ~= nil then
		client_ip_arr[fd] = data.ip
		client_ip = data.ip
	end
	if NEED_AUTH == true and data.account == '' and (data.code or data.token or data.openid )then
		cmd_rs = NetCmd.S2C_LOGIN 
		local wechat_ret = nil
		wechat_ret,expires_time,password = skynet.call("LOGIN", "lua", "login_auth_wechat",{ code=data.code, token=data.token, openid=data.openid})
		if wechat_ret == 1 or wechat_ret == 2 then
			msg = "第三方认证不通过"
			send_package(fd, {cmd=cmd_rs, ok=ok, msg=msg})
			return
		end
		
		if not wechat_ret.unionid then
			msg = "微信登录失败"
			send_package(fd, {cmd=cmd_rs, ok=ok, msg=msg})
			return
		end 
			
		account = wechat_ret.unionid  --只有一个UnionID，可以在用户管理-获取用户基本信息（UnionID机制）
		uid,status = skynet.call("LOGIN", "lua", "login_get_uid", {account=account},1)
		if not uid then
			-- 创建账户
			if not wechat_ret.sex then 
				wechat_ret.sex = 1
			end
			uid= skynet.call("MG_SAVE", "lua", "insert_user", account, wechat_ret.nickname, wechat_ret.sex, wechat_ret.headimgurl, client_ip,'wechat',password,expires_time )
			if not uid then
				msg = "创建账户失败"
				send_package(fd, {cmd=cmd_rs, ok=ok, msg=msg})
				return
			end
		elseif status == 1 then 
			msg = "您的账户封号状态"
			send_package(fd, {cmd=cmd_rs, ok=ok, msg=msg,code=ErrNo.FENGHAO})
			return
		else
			skynet.call("MG_SAVE", "lua", "set_user", uid, nil, nil, {
				name=wechat_ret.nickname,
				head=wechat_ret.headimgurl,
				sex=wechat_ret.sex,
				password=password,
				expires_time=expires_time,
				ip=client_ip,
				last_login_time=os.time()
				} 
			)
		end	 
		
	else
		account = data.account
		password = data.password
		local status = 0
		uid,status,expires_time = skynet.call("LOGIN", "lua", "login_get_uid", {account=account,password=password},2)
		if expires_time == 0 then 
			expires_time = os.time() + 3600
		end
		if not uid then
			-- 创建账户
			-- expires_time = os.time() + 86400
			-- uid = skynet.call("MG_SAVE", "lua", "insert_user", account, account, 1, '', '','self','123456', expires_time )
			-- if not uid then
			-- 	msg = "创建账户失败"
			-- 	send_package(fd, {cmd=NetCmd.S2C_LOGIN, ok=ok, msg=msg})
			-- 	return
			-- end
			
			msg = "账户过期，请重新登录"
			send_package(fd, {cmd=cmd_rs, ok=ok, msg=msg,code=ErrNo.ACCOUNT_PASSWORD_ERROR})
			return
		elseif status == 1 then 
			msg = "您的账户封号状态"
			send_package(fd, {cmd=cmd_rs, ok=ok, msg=msg,code=ErrNo.FENGHAO})
			return
		else
			skynet.call("MG_SAVE", "lua", "set_user", uid, nil, nil, {ip=client_ip,last_login_time=os.time()} )
		end
	end
	
	local client_agent = uid_to_agent[uid] and uid_to_agent[uid].agent or nil
	ok = true
	msg = "登陆成功"
	local server_time = os.time()
	send_package(fd, {cmd= cmd_rs, code=0,ok=ok, uid=uid, msg=msg, account = account, password = password,expires_time=expires_time,server_time=server_time})
	
	-- agent 登录
	if not client_agent then
		-- 创建agent
		client_agent = skynet.newservice("agent")
		agent[fd] = {client_agent, account, uid}
		skynet.call(client_agent, "lua", "start", gate, fd, account, uid)
	else
		-- 判断是否为同一个fd
		
		local tmp_fd = uid_to_agent[uid].fd
		if tmp_fd ~= fd then 
			local addr = uid_to_agent[uid].addr
		--	send_package(tmp_fd, {cmd=NetCmd.S2C_LOGIN_OTHER}) 
			local a = agent[tmp_fd]
			if a then
				skynet.send(a[1], "lua", "close")
				client_ip_arr[tmp_fd] = nil
		        agent[tmp_fd] = nil
			end
			skynet.call(gate, "lua", "kick", tmp_fd) 
			--删除心跳包
			if uid_to_agent[uid] and uid_to_agent[uid].heartbeat then 
				del_skynet_timer(uid_to_agent[uid].heartbeat)
			end
		end
		
		skynet.send(client_agent, "lua", "close", true)
		if uid_to_agent[uid] and uid_to_agent[uid].fd then 
			agent[uid_to_agent[uid].fd] = nil
			client_ip_arr[uid_to_agent[uid].fd] = nil
		end
		
		client_agent = skynet.newservice("agent")
		agent[fd] = {client_agent, account, uid}
		skynet.call(client_agent, "lua", "start", gate, fd, account, uid)
		--skynet.call(client_agent, "lua", "reconnect", gate, fd)
	end
    time_out_stop[fd] = nil
	
	account_to_agent[account] = client_agent
	uid_to_agent[uid] = {agent=client_agent,fd=fd,addr=gate}
	
	--删除心跳包
	if uid_to_agent[uid] and uid_to_agent[uid].heartbeat then 
		del_skynet_timer(uid_to_agent[uid].heartbeat)
	end

	local wait_time = 5

	local timer_cb = function()       -- 定时器触发
		if not uid or not uid_to_agent[uid] then 
			return 
		end
		
		if uid_to_agent[uid].agent and uid_to_agent[uid].heartbeat_time and uid_to_agent[uid].heartbeat_time == 3 then 
			skynet.call(uid_to_agent[uid].agent, "lua", "heart", fd)
		end
		
		if uid_to_agent[uid].heartbeat_time and uid_to_agent[uid].heartbeat_time == 4 then 
			del_skynet_timer(uid_to_agent[uid].heartbeat)
			skynet.call(uid_to_agent[uid].agent, "lua", "kick", fd)
			return
		end
    
        send_package(fd, {cmd=NetCmd.S2C_HEARTBEAT, uid=uid})
		if not uid_to_agent[uid].heartbeat_time then 
			uid_to_agent[uid].heartbeat_time = 0
		end
		uid_to_agent[uid].heartbeat_time = uid_to_agent[uid].heartbeat_time + 1
    end 
	--local timer_id =  add_skynet_timer2(wait_time,timer_cb)
	--uid_to_agent[uid].heartbeat = timer_id
	--uid_to_agent[uid].heartbeat_time = 0
	
end

function POST.heartbeat(uid)
	if uid and uid_to_agent[uid] then
		send_package(uid_to_agent[uid].fd, {cmd=NetCmd.S2C_HEARTBEAT, uid=uid})
	end
	-- if uid and uid_to_agent[uid] then
	-- 	local heartbeat_time = uid_to_agent[uid].heartbeat_time
	-- 	uid_to_agent[uid].heartbeat_time = 0
	-- 	if  uid_to_agent[uid].agent then 
	-- 		skynet.call(uid_to_agent[uid].agent, "lua", "heart_inline", fd)
	-- 	end
	-- end
end

-- 用户注册
function REQUEST.register(fd,data)
    local ok = false

    -- 检查是否已经有账号角色
    local uid = skynet.call("LOGIN", "lua", "login_get_uid", {account=data.account}, 1)
    if uid then
        msg = "该账号已经有角色了"
        send_package(fd, {cmd=NetCmd.S2C_REGISTER, ok=ok, msg=msg})
        return
    end

    -- 注册账号
    local ok, ret = skynet.call("LOGIN", "lua", "login_register",{account=data.account,password = data.password, token = data.token,})
    if not ok then
        send_package(fd, {cmd=NetCmd.S2C_REGISTER, ok=ok, msg=ret})
        return
    end

    local uid = ret
    ok = true
    msg = "注册成功"
    send_package(fd, {cmd=NetCmd.S2C_REGISTER, ok=ok, uid=uid, msg=msg})
end

function REQUEST.charge(fd, data)
	
	-- 验证参数
	if not data or not data.uid  then
		skynet.error("charge params error")
		return
	end
	
	-- insert into 数据库
	data.status = 0
	data.create_time = os.time()
	local ok= skynet.call("MG_SAVE", "lua", "insert_order", data)
    if ok == 0 then
        skynet.error("repeat order")
		return
    end
	
	data.uid = math.ceil(data.uid)
	local uid = data.uid
	-- 在线的话，添加元宝
	if uid_to_agent[uid] and uid_to_agent[uid].agent then 
		-- todo
		local client_agent = uid_to_agent[uid].agent
		skynet.call(client_agent, "lua", "charge", data)
	else
		-- update order
		data.status = 1 -- 发货成功
		skynet.call("MG_SAVE", "lua", "update_order_status", data)
		skynet.call("MG_SAVE", "lua", "update_card", data, Common.OPCharge)
	end
end

function REQUEST.add_card(fd, data)
	if not data or not data.uid  then
		skynet.error("add card params error")
		return
	end
	
	data.uid = math.ceil(data.uid)
	local uid = data.uid
	-- 验证签名
	if not data.sign or not data.uid or not data.card or not data.card_ex or not data.gm_order_id then 
		return 
	end 
end
function REQUEST.board_msg(fd, data)
	if data.send_type == 1 then 
		for fd,v in pairs(agent) do --0 主页重复展示 1一次展示 2 紧急通知
			send_package(fd,{ cmd = NetCmd.S2C_BROAD, content = data.content, typ = data.type})
		end
	else
		--间隔推送
		for fd,v in pairs(agent) do
			local send_board = function()
				send_package(fd,{ cmd = NetCmd.S2C_BROAD, content = data.content, typ = data.type})
			end
			add_skynet_timer3(data.jiange, send_board, data.times)
		end
	end
end

function REQUEST.game_info(fd, data)
	local rs = {} 
	time_out_stop[fd] = nil 
	if data.room_id and data.room_id > 0 then
		local room_id = math.ceil(data.room_id)
		local hall_name = skynet.call("MG_SAVE", "lua", "hall_name_hget", room_id)
		local room_fd = skynet.call(hall_name, "lua", "get_rooms", room_id)
		if room_fd then
	        local room_info_user,room_info = skynet.call( room_fd, "lua", "get_users" ) 

	        rs.room_info_user = room_info_user
	        rs.room_info = room_info
		end 
	end
	rs.user_ret = 0
	if data.uid and data.uid > 0 then
		if uid_to_agent[data.uid] then 
	        rs.user_ret = 1
		end 
	end
	rs.cmd = NetCmd.S2C_WEB_GAME_INFO
	send_package(fd, rs )
end

function REQUEST.web_user(fd, data)
	local rs = {} 
	rs.user_ret = 0
	if data.uid and data.uid > 0 then
		if uid_to_agent[data.uid] then 
	        rs.user_ret = 1
		end 
	end
	if data.fid > 0 and data.uid > 0 and uid_to_agent[data.uid] then 
		rs.ret = skynet.call(uid_to_agent[data.uid].agent, "lua", "web_is_uid_fid", data.fid)
	end
	rs.cmd = NetCmd.S2C_WEB_USER
	send_package(fd, rs )
end

function REQUEST.web_error(fd, data)
    local text = json.encode(data)
	packdeal.writelog_error('error',text)
end


function REQUEST.web_join_friend(fd, data)
	local rs = {} 
	rs.ret = 0
	if data.fid > 0 and data.uid > 0 then 
		local close_ag = 0
		if uid_to_agent[data.uid] == nil then
			close_ag = 1
			uid_to_agent[data.uid].agent = skynet.newservice("agent") 
			skynet.call(uid_to_agent[data.uid].agent, "lua", "start", gate, fd, data.uid, data.uid)
		end
		rs.ret = skynet.call(uid_to_agent[data.uid].agent, "lua", "web_friends_add", data.fid)
		if close_ag == 1 then 
			skynet.send(uid_to_agent[data.uid].agent, "lua", "destory")
			skynet.call(gate, "lua", "kick", fd) 
			uid_to_agent[data.uid] = nil
		end
	end
	rs.cmd = NetCmd.S2C_WEB_JOIN_FRIEND
	send_package(fd, rs )
end

function REQUEST.web_join_room(fd, data)
	local rs = {} 
	rs.ret = 0
	if data.room_id > 0 and data.uid > 0 and uid_to_agent[data.uid] ~= nil then 
		if uid_to_agent[data.uid].agent ~= nil then 
			rs.ret = skynet.call(uid_to_agent[data.uid].agent, "lua", "web_join_room", data.room_id)
			if rs.ret == 1 then  
				skynet.send(uid_to_agent[data.uid].agent, "lua", "destory")
				skynet.call(gate, "lua", "kick", uid_to_agent[data.uid].fd) 
				client_ip_arr[uid_to_agent[data.uid].fd] = nil
		        agent[uid_to_agent[data.uid].fd] = nil
				uid_to_agent[data.uid] = nil  
			end
		end
	end
	rs.cmd = NetCmd.S2C_WEB_JOIN_ROOM
	send_package(fd, rs )
end

function REQUEST.return_now_client_number(fd, data)
	local rs = {} 
	rs.now_num = skynet.call(gate, "lua", "return_now_client_number", fd)
	rs.cmd = NetCmd.S2C_RETURN_NOW_CLIENT_NUMBER
	send_package(fd, rs )
end

function REQUEST.close_room(fd, data)
	local room_id = math.ceil(data.room_id)
	local hall_name = skynet.call("MG_SAVE", "lua", "hall_name_hget", room_id)
	if hall_name == nil or hall_name == false then 
		hall_name = 'NIUNIU_HALL'
	end
	skynet.call(hall_name, "lua", "close_room", room_id)
end
REQUEST.func_map = {
    [ NetCmd.C2S_LOGIN_ACCOUNT ]             		= REQUEST.login,
    [ NetCmd.C2S_LOGIN ]             		= REQUEST.login,
    [ NetCmd.C2S_REGISTER ]          		= REQUEST.register,
	[ NetCmd.C2S_CHARGE ]            		= REQUEST.charge,
	[ NetCmd.C2S_ADD_CARD 	]        		= REQUEST.add_card,
	[ NetCmd.C2S_BROAD 		]           	= REQUEST.board_msg,
	[ NetCmd.C2S_WEB_GAME_INFO 		]           	= REQUEST.game_info,
	[ NetCmd.C2S_WEB_JOIN_ROOM 		]           	= REQUEST.web_join_room,
	[ NetCmd.C2S_RETURN_NOW_CLIENT_NUMBER ] = REQUEST.return_now_client_number,

	[ NetCmd.C2S_WEB_USER 		]           	= REQUEST.web_user,
	[ NetCmd.C2S_WEB_JOIN_FRIEND 		]       = REQUEST.web_join_friend,
	[ NetCmd.C2S_WEB_ERROR 		]       = REQUEST.web_error,
}

function SOCKET.open(fd, addr)
	local index = string.find(addr,":")
	local ip =  string.sub(addr,1,index-1)
	client_ip_arr[fd] = ip
	skynet.call(gate, "lua", "accept", fd)


	local timer_time_out_stop = function()       -- 定时器触发 
		if time_out_stop[fd]  then 
		 	skynet.call(gate, "lua", "kick", fd)  
		end 
	end
	time_out_stop[fd] =  add_skynet_timer(30,timer_time_out_stop) 
end

-- 通知agent连接断开
local function close_agent(fd)
    local ok = false
	local a = agent[fd]
	if a then
		skynet.send(a[1], "lua", "close")
		client_ip_arr[fd] = nil
        agent[fd] = nil
	end
	skynet.call(gate, "lua", "kick", fd) 
end

function SOCKET.close(fd)
	--skynet.error("socket close",fd)
	close_agent(fd)
end

function SOCKET.error(fd, msg)
	--skynet.error("socket error",fd, msg)
	close_agent(fd)
end

function SOCKET.warning(fd, size)
	-- size K bytes havn't send out in fd
	-- skynet.error("socket warning", fd, size)
end

function SOCKET.data(fd, msg) 
	if msg == nil or string.len(msg) < 5 then
		return 
	end
	-- print(fd.."==client_send_data=============="..msg)
	local json_data = json.decode(msg)
	if not json_data  then
		-- skynet.error(string.format("invalid json data, fd: ",fd)) 
		return
	end 
	local f = REQUEST.func_map[json_data.cmd]
	if not f then
    	send_package(fd, {cmd=NetCmd.S2C_VERSION, uid=0}) 
		-- skynet.error(string.format("invalid json cmd:%d, fd:%d", json_data.cmd, fd)) 
		return
	end
	
	if json_data.cmd == NetCmd.C2S_BROAD or json_data.cmd == NetCmd.S2C_LOGIN or json_data.cmd >= NetCmd.C2S_WEB_GAME_INFO then
		f(fd,json_data)
	else
		make_queue(fd,json_data,f)
	end 
end
 

function CMD.start(conf)
	skynet.call(gate, "lua", "open" , conf)
 
end

function CMD.close(fd)
	close_agent(fd)
end

function CMD.close_agent(fd)
	close_agent(fd)
end

function POST.destory(account, uid) 
	-- skynet.error("watchlog destory",account,uid)
    account_to_agent[account] = nil 
    uid_to_agent[uid] = nil 
end

skynet.start(function()
	skynet.dispatch("lua", function(session, source, cmd, subcmd, ...)
		if cmd == "socket" then
			local f = SOCKET[subcmd]
			f(...)
			-- socket api don't need return
		else
            local f = CMD[cmd]
            if f then
                skynet.ret(skynet.pack(f(subcmd,...)))
            else
                f = POST[cmd]
                if f then
                    f(subcmd,...)
                end
            end

		end
	end)

	gate = skynet.newservice("wsgate")
end)

